/* NoX (NoC Simulator)
 *
 * Dept. of Computer Science & Engineering, Pennsylvania State University.
 * All Rights Reserved.
 *  
 * 1. License     
 * NoX is distributed free of charge for academic, educational, noncommercial 
 * research purposes as long as this notice in its entirety is preserved in
 * every file included in this package.
 * All commercial use of this program requires separate licence. Contact the
 * author for details.
 * 
 * 2. All the publications that used the simulation results generated by the 
 * NoX should notify the author of the publication information and put 
 * following reference.
 *
 *  http://www.cse.psu.edu/~dpark/nox/
 * 
 * 3. Modification of the source code is permitted and encouraged as long as 
 * it follows the terms described in this copyright notice.
 *
 * 4. The author is not responsible for any problems caused by possible errors
 * of the NoX package. Therefore, users should verify the simulation result
 * before using it in their publication.
 *
 * Dept. of Computer Science & Engineering, Pennsylvania State University.
 * Contact: dpark@cse.psu.edu 
 * 
 * 6. If problems are found with the NoX package, please send an email to the
 * author for discussion and correction.

 */

/* Update History
 *
 *
 */

/* ROUTER_COMMON.C - Functions shared by various routing algorithms */

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <zlib.h>
#include <vector>
#include <algorithm>

#include "router.h"
#include "main.h"
#include "nic.h"
#include "link.h"
#include "app.h"
#include "router_common.h"
#include "sim_result.h"
#include "defines.h"
#include "shared.h"
#include "batch.h"
#include "rank.h"
#include <cstring>

#ifdef TR_INTEG
	#undef INVALID
	#include "processor.h"
	#include "globals.h"

	#undef INVALID
	#define INVALID 1
#endif

void calc_coord(int node, int *x, int *y)
{
  *x = node % NUM_COLS;
  *y = node / NUM_COLS;
}

int calc_dist(int n1, int n2)
{
  int x1,y1,x2,y2;
  
  x1 = n1 % NUM_COLS; y1 = n1 / NUM_COLS;
  x2 = n2 % NUM_COLS; y2 = n2 / NUM_COLS;

  return (abs(x1-x2) + abs(y1-y2));

}


// For each PC of a node, compare the credits of all the VCs within that PC to
// find the maximum credit value and corresponding VC number.
// Then store the maximum credit and the VC number in the 'credit' and 'best_vc' 
// arrays respectively.
// Note that the VC value assigned by routing algorithms based on the 'best_vc' 
// array can be overwritten with the value assigned by the VA(Virtual channel 
// Allocator) later unless you don't use separate VA. 
void get_best_credit(int node, int credit[MAX_PC-MAX_NIC_PC], int best_vc[MAX_PC-MAX_NIC_PC])
{
  int next_node, next_pc, pc, vc, vc_index, tmp_max, tmp_vc;
  static int last_used_vc[MAX_NODES][MAX_PC] = {0};

  for(pc=0; pc<NUM_PC - NUM_NIC_PC; pc++)
  {
    tmp_max=-1;
    tmp_vc =-1;
    next_node = neighbor[node][pc];

    if(next_node != EDGE && router[next_node].health_status != FAIL)
    {
      //      next_pc = (pc+(NUM_PC-NUM_NIC_PC)/2) % (NUM_PC-NUM_NIC_PC);
      next_pc = neighbor_pc[node][pc];

      // Calculate the maximum credit.
      for(vc_index=1; vc_index<=NUM_VC; vc_index++)
      {
        vc = (last_used_vc[node][pc] + vc_index) % NUM_VC;
        if(routing_algorithm == AD)
        {
          if( (topology == MESH && vc == 0) || 
              (topology == TORUS && vc < 2) )
            continue;
        }

        if( is_ready[next_node][next_pc][vc].rinbuf > tmp_max)
        {
          tmp_max = is_ready[next_node][next_pc][vc].rinbuf;
          tmp_vc  = vc;
        }// if
      }// for
    }// if
    credit[pc]  = tmp_max; // Best credit among all VCs
    best_vc[pc] = tmp_vc;  // Best VC
    last_used_vc[node][pc] = tmp_vc;
  }
}

// Check the blocking (node/link fail, edge node)
void get_blocked(int node, int *is_blocked)
{
  int cur_dir;
  int nneighbor;

  for (cur_dir=WEST; cur_dir<=SOUTH; cur_dir++) 
  {
    nneighbor = neighbor[node][cur_dir];

    // We have to consider both node and link status to decide whether 
    // the path is blocked or not. For this, we use FA_link_info here.
    if(router[node].FA_blocked_info[cur_dir] == BLOCK) 
      is_blocked[cur_dir] = YES; 
    else
      is_blocked[cur_dir] = NO;
  }
}
//VC allocation
//in_pc & out_pc are in the current router
int get_best_vc(int node, int pc, int vc, int out_pc, flit_t *flit_ptr)
{
  int min_range, max_range, tmp_max, i, flag, next_node, next_pc, dest_vc=0;
  static int last_used_vc[MAX_NODES][MAX_PC] = {0};
  int NUM_NEXT_VC = 6;
  int SPARE_VC=-1;
  
  next_node = neighbor[node][out_pc];
  next_pc   = neighbor_pc[node][out_pc];
  if(out_pc >= NUM_PC - NUM_NIC_PC)
    NUM_NEXT_VC = router_info[node].num_vc;
  else
    NUM_NEXT_VC = router_info[next_node].num_vc;
  //SPARE buffer logic for priority inversion
  if(ARB_TYPE == PR && age_based_arbitration == false)
  {
    SPARE_VC = router_info[next_node].num_vc - 1;
    NUM_NEXT_VC--;
  }


  int priority_level = flit_ptr->priority%num_priority_levels;

  // Above function does not set dest_vc value.
  // Set VC considering Duato & Dally's deadlock recovery algorithms.
  if(topology == TORUS)
  {
    // Check current direction (out_pc) and assign VCs.
    // 1. if      out_pc is either LEFT or RIGHT and sx < dx, then use higher VCs.
    // 2. else if out_pc is either LEFT or RIGHT and sx > dx, then use lowerr VCs.
    // 3. else if out_pc is either UP   or DOWN  and sy < dy, then use higher VCs.
    // 4. else if out_pc is either UP   or DOWN  and sy > dy, then use lower  VCs.
    // 5. else if out_pc is ejection channel, then use whole VCs.
    if 
      ( ((out_pc == LEFT || out_pc == RIGHT) && flit_ptr->is_sx_less_than_dx == YES) || // case 1
        ((out_pc == UP   || out_pc == DOWN ) && flit_ptr->is_sy_less_than_dy == YES) )  // case 3
      { min_range = (NUM_NEXT_VC/2);  max_range = NUM_NEXT_VC;     } // Use higher VC set
    else if
      ( ((out_pc == LEFT || out_pc == RIGHT) && flit_ptr->is_sx_less_than_dx == NO ) || // case 2
        ((out_pc == UP   || out_pc == DOWN ) && flit_ptr->is_sy_less_than_dy == NO ) )  // case 4
      { min_range = 0;           max_range = (NUM_NEXT_VC/2); } // Use lower VC set
    else if (out_pc == THIS) // case 5, Ejection 
    { min_range = 0;             max_range = NUM_NEXT_VC; }

  }
  else // MESH topology
  {
    min_range = 0; max_range = NUM_NEXT_VC;
  }
  
  //if(flit_ptr->priority != num_priority_levels-1 && out_pc < NUM_PC - NUM_NIC_PC)
    //min_range += 1;
  int RankNode = flit_ptr->priority_id;
#ifdef TR_INTEG  
  if(!(strstr(processor[RankNode].app_name,"perl")) /*&& out_pc < NUM_PC - NUM_NIC_PC*/)
    min_range += 2;
#endif


  if(PRIORITY_VC_SET  == YES)
  {
    int pri_id = flit_ptr->priority;
    int start_vc = min_range + pri_id*((max_range-min_range)/num_priority_levels);
    int end_vc = start_vc + (max_range-min_range)/num_priority_levels; 
    min_range = start_vc; max_range=end_vc;
  }

  //if( out_pc < NUM_PC - NUM_NIC_PC)
  {
    // Gather credit values of the VCs within out_pc. 
    // Then find the VC with the maximum available credit.
    // Here, exclude already reserved VCs since they cannot be used for a new HEAD flit.
    tmp_max = -1;
    flag = -1;

    for(int vc_index=1; vc_index<=NUM_NEXT_VC; vc_index++)
    {
      i = (last_used_vc[node][pc] + vc_index) % NUM_NEXT_VC;
      if( is_ready[next_node][next_pc][i].rinbuf > tmp_max  && // Check credit of next node
          is_ready[next_node][next_pc][i].xbarin == YES  && // Check path reservation status
          i >= min_range && i < max_range)     //Check VC range for preventing deadlock                        
      {
        tmp_max = is_ready[next_node][next_pc][i].rinbuf;
        dest_vc = i;
        flag = 1;
      }
    }
    // If no VCs are available, arbitrarily choose one of them within the available range.
    //does not really matter if you are doing VC allocation every cycle, if not
    //makes a big difference
    if(flag == -1)
    {
      dest_vc = min_range + (int)((float)(max_range - min_range - 1) * (rand() / (float)(RAND_MAX)));
      //if(out_pc < NUM_PC - NUM_NIC_PC)
      {
        tmp_max = get_max_priority_vc(next_node,next_pc);
        dest_vc = tmp_max == -1 ? dest_vc : tmp_max;
        //SPARE buffer logic for priority inversion
        if(ARB_TYPE == PR && age_based_arbitration == false)
        {
          //if(flit_ptr->priority_inversion_cycles >= 1 && 
          int RankNode = flit_ptr->priority_id;
          if((strstr(processor[RankNode].app_name,"perl")) &&
              is_ready[next_node][next_pc][SPARE_VC].xbarin == YES) // Check path reservation status
          {
            dest_vc = SPARE_VC;
            if(verbose == YES)
              printf("Assigning Spare VC dest_vc:%d max_range:%d\n",dest_vc,max_range);
          }
        } 
      }
    }
    
    last_used_vc[node][pc]=dest_vc; 
    if(!(dest_vc >= 0 && dest_vc< router_info[node].num_vc))
    {
      printf("Dec/RT Error for [%d][%d][%d]-flit:%d(%s) to dest node:%d dest pc:%d dest vc:%d at %lld\n", node, pc, vc, flit_ptr->flit_num, 
          (HEAD_FLIT)?"HEAD":(TAIL_FLIT)?"TAIL":"MIDDLE", neighbor[node][out_pc], out_pc,dest_vc,(long long)sim_clock); 
      printf("[%d][%d][%d] \n",neighbor[node][out_pc],neighbor_pc[node][out_pc],dest_vc);
      print_mbox(&(router_input_buf[neighbor[node][out_pc]][neighbor_pc[node][out_pc]][dest_vc]));
      //exit(1);

    }
  }

  if(verbose == YES)
   printf("VC Allocation for [%d][%d][%d]-flit:%d(%s) to [%d][%d][%d] chosen dest vc:%d at %lld\n", node, pc, vc, flit_ptr->flit_num, 
          (HEAD_FLIT)?"HEAD":(TAIL_FLIT)?"TAIL":"MIDDLE", next_node, next_pc,dest_vc,dest_vc, (long long)sim_clock); 
  return dest_vc;
}

int get_min_priority_vc(int node, int pc)
{
  int tmp_min = 64;
  int NUM_VC = router_info[node].num_vc;
  int min_vc=-1;

  if(ARB_TYPE == RR)
    return -1;
  //FIXME works on for mesh currently
  for(int vc=0; vc<NUM_VC; vc++)
  {
    int pri = mbox_priority(&(router_input_buf[node][pc][vc]));
    if(pri  < tmp_min) // Check credit of next node
    {
      tmp_min = pri; 
      min_vc = vc;
    }
  }
  return min_vc;
}

int get_max_priority_vc(int node, int pc)
{
  int tmp_max = -1;
  int NUM_VC = router_info[node].num_vc;
  int max_vc=-1;

  if(ARB_TYPE == RR)
    return -1;
  //FIXME works on for mesh currently
  for(int vc=0; vc<NUM_VC; vc++)
  {
    int pri = mbox_priority(&(router_input_buf[node][pc][vc]));
    //printf("node:%d pc:%d vc:%d pri:%d\n",node,pc,vc,pri);
    if(pri  > tmp_max) // Check credit of next node
    {
      tmp_max = pri; 
      max_vc = vc;
    }
  }
  return max_vc;
}

int get_max_priority(int node, int pc)
{
  int tmp_max = -1;
  int NUM_VC = router_info[node].num_vc;
  int max_vc=-1;

  if(ARB_TYPE == RR)
    return -1;
  //FIXME works on for mesh currently
  for(int vc=0; vc<NUM_VC; vc++)
  {
    int pri = mbox_priority(&(router_input_buf[node][pc][vc]));
    //printf("node:%d pc:%d vc:%d pri:%d\n",node,pc,vc,pri);
    if(pri  > tmp_max) // Check credit of next node
    {
      tmp_max = pri; 
      max_vc = vc;
    }
  }
  return tmp_max;
}


bool is_rinbuf_vc_free(int node, int pc, int vc)
{
  bool free = true;
  if(is_ready[node][pc][vc].xbarin == NO)
  { 
    free = false; 
    if(verbose == YES)
      printf("xbarin NO [%d][%d][%d]\n",node,pc,vc);
  }
  if(is_ready[node][pc][vc].rinbuf == NO)
  { 
    free = false; 
    if(verbose == YES)
      printf("rinbuf NO [%d][%d][%d]\n",node,pc,vc);
  }
  //if(pc >=(NUM_PC - NUM_NIC_PC) && rinbuf_info[node][pc -(NUM_PC - NUM_NIC_PC)][vc].vc_stat == VC_BUSY)
    //free = false;
  if(pc >=(NUM_PC - NUM_NIC_PC) && msg_cnt(&(router_input_buf[node][pc][vc])) != 0)
  { 
    free = false; 
    if(verbose == YES)
      printf("msg count NO [%d][%d][%d]\n",node,pc,vc);
  }
  //if(free == true)
    //printf("yaya\n");
  return free;
}

void get_best_nic_vc(int node, int pc, int *nic_vc, int *rinbuf_vc)
{
  int req[MAX_VC];
  static int last_win[MAX_NODES][MAX_NIC_PC][MAX_VC];
  long long priority[MAX_VC], age[MAX_VC];
  flit_t *flit_ptr;
  int pc_index = pc + NUM_PC - NUM_NIC_PC;

  NUM_VC     = router_info[node].num_vc;

  //Intitalize 
  int i=0;
  for(i=0; i<NUM_NIC_VC; i++)
  {req[i] = 0; priority[i] = 0; age[i] = 0;}

  //Check rinbuf VC is available is or not
  int flag = -1;
  for(int k=0; k<NUM_VC; k++)
  {
    if(is_rinbuf_vc_free(node,pc_index,k))
    {flag=k; break;}
  }

  //Prepare request table and priority and age
  for(i=0; i<NUM_NIC_VC; i++)
    if(msg_cnt(&(nic_output_buf[node][pc][i])) > 0) 
    {
      read_flit(&(nic_output_buf[node][pc][i]), &flit_ptr);
      if(HEAD_FLIT)
      {
        if(flag != -1)
        {
          req[i] = 1; priority[i] = flit_ptr->priority;
          age[i] = sim_clock - flit_ptr->msg_inj_tm;
          if(age_based_arbitration)
            priority[i] = sim_clock - flit_ptr->msg_inj_tm;
          if(enable_app_age)
            age[i] = sim_clock - flit_ptr->app_inj_tm;
        }
      }
      else
      {
        int out_vc = vc_nic_info[node][pc][i].out_vc;
        if(is_ready[node][pc_index][out_vc].rinbuf != NO)
        {
          req[i] = 1; priority[i] = flit_ptr->priority;
          age[i] = sim_clock - flit_ptr->msg_inj_tm;
          if(age_based_arbitration)
            priority[i] = sim_clock - flit_ptr->msg_inj_tm;
          if(enable_app_age)
            age[i] = sim_clock - flit_ptr->app_inj_tm;
        }
      }
    }
  //Arbitrate
   if(ARB_TYPE == PR)
      PR_arbiter(req, last_win[node][pc-(NUM_PC-NUM_NIC_PC)], priority, age, 1, NUM_NIC_VC);
   else if(ARB_TYPE == RR)
    RR_arbiter(req, last_win[node][pc-(NUM_PC-NUM_NIC_PC)], 1, NUM_NIC_VC);

  //return values
  *rinbuf_vc = flag;
  *nic_vc = -1;
  for(i=0; i<NUM_NIC_VC; i++)
    if(req[i] == 1)
      *nic_vc = i;
  return;
}
